import compose from "./compose";
import { ContextType, MiddlewareType } from "../types";

/**
 * 云函数程序
 */
export class Application {
  /**
   * 中间件middleware数组
   */
  private middleware: MiddlewareType[] = [];

  /**
   * 注册中间件
   *
   * @api public
   * @param  {MiddlewareType} fn 中间件函数
   * @returns {this} 链式调用
   * @example
   * ```javascript
   * const app = new Application();
   *
   * app.use(async (ctx, next) => {
   *   console.time("计时");
   *   // 执行next()进入下一个中间件
   *   await next();
   *   console.timeEnd("计时");
   * });
   * ```
   */
  public use(fn: MiddlewareType): this {
    // 中间件必须是一个函数，不然就报错
    if (typeof fn !== "function") {
      throw new TypeError("Middleware must be a function!");
    }

    // 将接收到的中间件塞入到middleware数组就行
    this.middleware.push(fn);
    return this;
  }

  /**
   * 创建上下文ctx对象的函数
   *
   * @api private
   * @param {Object} event 客户端调用云函数时传入的参数
   * @param {Object} context 客户端调用云函数时的系统运行环境参数
   * @returns {ContextType} 程序上下文对象
   */
  private createContext(
    event: Record<string, any>,
    context: Record<string, any>
  ): ContextType {
    // 云函数调用来源为 timing 转换为路径
    if ("timing" === context.SOURCE) {
      context = Object.assign({ timing: event }, context);
      const { timingTriggerConfig, timestamp } = event;
      // 定时cron表达式转换路由 hex编码化
      event = {
        url: Buffer.from(timingTriggerConfig, "utf-8").toString("hex"),
        method: "PUT",
        data: { timestamp },
      };
    }

    // 云函数调用来源为 http 需要参数转化
    if ("http" === context.SOURCE) {
      context = Object.assign({ headers: event.headers }, context);
      const { isBase64Encoded, body } = event;
      try {
        // 解析 Base64 编码的字符串
        if (isBase64Encoded) {
          const bodyStr = Buffer.from(body, "base64").toString("utf-8");
          event = JSON.parse(bodyStr);
        } else {
          event = JSON.parse(body);
        }
      } catch (error: any) {
        throw error;
      }
    }

    // 初始上下文参数
    const { url, method, data } = event;
    let pathname: string = "";
    let search: string = "";
    let query: Record<string, any> = {};

    // 解析 url 参数
    if (url) {
      const parseUrl = new URL(url, "https://cloud.com/");
      pathname = parseUrl.pathname;
      search = parseUrl.search;
      pathname = parseUrl.pathname;
      query = Object.fromEntries(Array.from(parseUrl.searchParams.entries()));
    }

    return {
      event: event,
      app: context,
      url: url || "",
      method: (method || "GET").toUpperCase(),
      data: data || {},
      path: pathname,
      querystring: search,
      query: query,
      params: {},
    };
  }

  /**
   * 入口监听函数
   *
   * @api public
   * @param {Object} event 客户端调用云函数时传入的参数
   * @param {Object} context 客户端调用云函数时的系统运行环境参数
   * @param {Object} config 全局配置文件对象，挂载到全局上下文请求
   * @returns {Promise} 异步函数
   * @example
   * ```javascript
   * const app = new Application();
   *
   * app.listen(event, context, config).then(res => {
   *   console.log("响应ctx.body的结果：", res);
   * }).catch(err => {
   *   console.log("错误结果：", err);
   * });
   * ```
   */
  public async listen(
    event: Record<string, any>,
    context: Record<string, any>,
    config?: Record<string, any>
  ): Promise<any> {
    // 创建上下文ctx对象的函数
    const ctx: ContextType = this.createContext(event, context);

    // 全局配置属性
    if (typeof config === "object" && config) {
      ctx.config = config;
    }

    // compose来自koa-compose库，就是将中间件合并成一个函数
    await compose(this.middleware)(ctx);

    // 响应body属性
    return await Promise.resolve(ctx.body || {});
  }
}
